execfile('maarten/shared.py')
execfile('maarten/state.py')

class Agent(object):

    SAFE_MOVE = True

    # the goal is an (x, y) tuple that the agents moves toward
    goal = None

    NAME = "GORBACHE"

    def __init__(self, id, team, settings=None, field_rects=None,
                    field_grid=None, nav_mesh=None, blob=None, matchinfo=None):
        """ Each agent is initialized at the beginning of each game.
            The first agent (id==0) can use this to set up global variables.
            Note that the properties pertaining to the game field might not be
            given for each game.
        """
        self.id = id
        self.team = team
        self.mesh = nav_mesh
        self.grid = field_grid
        self.settings = settings
        self.goal = (-1, -1)
        self.callsign = '%s-%d'% (('BLU' if team == TEAM_BLUE else 'RED'), id)

        # Read the binary blob, this should contain the qTable. I'm not sure
        # how, though...
        blob_content = None
        self.blob_path = None
        if blob is not None and self.id == 0:
            # Remember the blob path so we can write back to it
            self.blob_path = blob.name
            blob_content = pickle.loads(blob.read())
            print "Agent %s received blob" % (
               self.callsign)
            # Reset the file so other agents can read it too.

        # initiate the shared object for the communication between the agents
        if id == 0:
            self.shared = self.__class__.shared = Shared(team, settings, blob_content)
            self.__class__.observation_processor = self.shared.preprocess_observation if team == TEAM_BLUE else self.shared.process_observation
        self.shared.add_agent(self)

    def observe(self, observation):
        """ Each agent is passed an observation using this function,
            before being asked for an action. You can store either
            the observation object or its properties to use them
            to determine your action. Note that the observation object
            is modified in place.
        """
        self.observation = observation

        # deepcopy this basterd because we shall mess with observation
        # (deepcopy is not really preferable but we did not know we needed to
        # copy it goin; in)
        observation_out = copy.deepcopy(observation)
        observation_out.previous_action = self.goal
        observation_out.angle += math.pi

        self.observation_processor(self, observation_out)

    def action(self):
        """ This function is called every step and should
            return a tuple in the form: (turn, speed, shoot)
        """
        # default values; do nothin'!
        turn = 0
        speed = 0
        shoot = False


        #update killcount
        lastHit = self.observation.hit
        if lastHit is not None and lastHit is not self.team:
            self.shared.kills += 1

        if self.observation.respawn_in < 1:
            self.deathCheck = False

        if self.observation.respawn_in  > 0 and self.deathCheck is False:
            self.shared.deaths += 1
            self.deathCheck = True


        path = False

        # if there is an enemy in range we might want to act on that
        (turn, speed, shoot) = self.determine_shoot()
        if shoot:
            return (turn, speed, shoot)

        # if we do not shoot we shall ask the big bad shared for our goal
        self.goal = self.shared.request_goal(self)

        # and calculate our path calculate our awesome path
        path = self.find_path_to_goal()

        # if a path has been found we will want to go to there
        if path:
            (turn, speed) = path
        else:
            # insert idle behaviour here
            pass

        return (turn, speed, shoot)

    def debug(self, surface):
        """ Allows the agents to draw on the game UI,
            Refer to the pygame reference to see how you can
            draw on a pygame.surface. The given surface is
            not cleared automatically. Additionally, this
            function will only be called when the renderer is
            active, and it will only be called for the active team.
        """
        import pygame


        color_line = (00, 00, 00)
        color_fov = (255, 00, 229, 100)
        color_grid  = (120,180,120)


        # First agent clears the screen
        if self.id == 0:
            surface.fill((0,0,0,0))
        if self.goal is not None:
            # rectangle of what the bot sees, always convenient
            rect = pygame.Rect(
                self.observation.loc[0] - (self.settings.max_see/2),
                self.observation.loc[1] - (self.settings.max_see/2),
                    70, 70)
            pygame.draw.rect(surface, color_fov, rect)

            # line to goal, always convenient
            pygame.draw.line(surface, color_line,
                self.observation.loc, self.goal)

        # draw something to identify the agents id
        id_color = [0, 0, 0]
        id_color[self.id] = 255
        rect = pygame.Rect( self.observation.loc[0],
            self.observation.loc[1], 5, 5)
        pygame.draw.rect(surface, id_color, rect)


        # print the mesh grid because we can
        if self.id == 2:
            for n1 in self.mesh:
                for n2 in self.mesh[n1]:
                    pygame.draw.line(surface,color_grid, n1, n2, 2)
                    pygame.draw.circle(surface, color_grid, n1, 3)

        if(self.observation.selected == True):
            print self.observation

    def finalize(self, interrupted=False):
        """ This function is called after the game ends,
            either due to time/score limits, or due to an
            interrupt (CTRL+C) by the user. Use it to
            store any learned variables and write logs/reports.
        """
        # Save the qvalues to a pickle
        if self.id == 0 and self.blob_path is not None and interrupted == False:
            try:
                # Try to dump the qTable into the blob
                blob_file = open(self.blob_path, 'wb')
                # Pickle it converted to a normal dict, since the default dict doesnt work
                pickle.dump(dict(self.shared.state_dict), blob_file, pickle.HIGHEST_PROTOCOL)
                print 'written pickle to file %s' % (self.blob_path)
            except:
                # We can't write to the blob, this is normal on AppEngine since
                # we don't have filesystem access there
                print "Agent %s can't write blob." % self.callsign
        pass


    def determine_shoot(self):
        """ Determines if the agent should attempt to shoot another agent
            and calculates the `turn' value if the result of this consideration
            is postive. Assumes the enemy is within range.
            returns (turn, 0, True) if the agent should shoot
            return (0, 0, False) if the agent should not shoot """
        if (self.observation.ammo > 0):
            # loop for each foe
            for foe_tuple in self.observation.foes:
                #print "foe: %s" % str(foe_tuple)
                foe = foe_tuple[0:2]

                # check if the hostile fellow is within range
                if (point_dist(self.observation.loc, foe) >
                        self.settings.max_range):
                    break;

                # check for friendly fire
                wont_hit_friendly = True
                for friend in self.observation.friends:
                    if(line_intersects_circ(self.observation.loc, foe,
                            friend, 6)):
                        wont_hit_friendly = False
                        break

                if wont_hit_friendly:
                    # calculate the turn
                    turn = self.calculate_turn_to_point(foe)

                    # check if we can make the turn
                    if abs(turn) <= self.shared.settings.max_turn:
                        # check if there is no wall in between motherfuckers
                        if not(line_intersects_grid(self.observation.loc,
                            foe[0:2],
                            self.grid, self.settings.tilesize)):
                            # then finally shoot
                            return (turn, 0, True)
        # if anything fails, we shall return NOTHING!
        return (0, 0, False)


    def get_turn_towards_enemy(self):
        enemy_location = self.shared.get_finest_enemy(self)
        if not(enemy_location):
            return 0
        return self.calculate_turn_to_point((enemy_location[0], enemy_location[1]))

    def find_path_to_goal(self):
        """ calculates the path to a goal
            goal is a (x, y) tuple
            returns (turn, speed) tuple when a path has been found
            returns false when no path has been found
        """
        path = find_path(self.observation.loc, self.goal,
            self.mesh, self.grid)

        if path:
            dx = path[0][0] - self.observation.loc[0]
            dy = path[0][1] - self.observation.loc[1]
            turn = angle_fix(math.atan2(dy, dx) - self.observation.angle)
            speed = (dx**2 + dy**2)**0.5

            if self.SAFE_MOVE and abs(turn) > self.settings.max_turn:
                speed = 0
            return (turn, speed)
        return False


    def calculate_turn_to_point(self, point):
        """ calculates a turn value that is required should
        the robot want to turn to a point) """
        dx = point[0] - self.observation.loc[0]
        dy = point[1] - self.observation.loc[1]
        return angle_fix(float(math.atan2(dy, dx)) -
            float(self.observation.angle))

